#region license
/*
 * Copyright (C) 2007 CchenSoft.com
 * 
 * This library is free software; you can redistribute it and/or modify it 
 * under the terms of the GNU Lesser General Public License 2.1 or later, as
 * published by the Free Software Foundation. See the included License.txt
 * or http://www.gnu.org/copyleft/lesser.html for details.
 * 
 * Author: Billy Zhang
 */
#endregion

using System.Collections;
using System;
using System.IO;
using System.Xml;
using System.Collections.Specialized;
using CchenSoft.Workflow.Spi;
using CchenSoft.Workflow.Loader;

namespace CchenSoft.Workflow.Config
{
    /**
     * Default implementation for a configuration object.
     * This configuration object is passed to the {@link com.opensymphony.workflow.Workflow#setConfiguration(Configuration)}
     * method. If the configuration is not initialized, the {@link #load(java.net.URL)} method will be called by
     * the workflow. Alternatively, the caller can explicitly load the configuration by calling that method before
     * calling {@link com.opensymphony.workflow.Workflow#setConfiguration(Configuration)}.
     * <p>
     * The loading behaviour comes into play when specifying a configuration remotely, for example in an EJB
     * environment. It might be desirable to ensure that the configuration is loaded from within the EJB server,
     * rather than in the calling client.
     */

    public class DefaultConfiguration : IConfiguration
    {
        //~ Static fields/initializers /////////////////////////////////////////////

        public static DefaultConfiguration INSTANCE = new DefaultConfiguration();

        //~ Instance fields ////////////////////////////////////////////////////////

        private IDictionary persistenceArgs = new Hashtable();
        private string persistenceClass;
        private IWorkflowFactory factory = new XMLWorkflowFactory();
        private IWorkflowStore store = null;
        private IVariableResolver variableResolver = new DefaultVariableResolver();
        private TypeResolver typeResolver = new TypeResolver();
        private bool initialized;

        //~ Methods ////////////////////////////////////////////////////////////////

        private DefaultConfiguration()
        {
            Load(null);
        }

        public bool IsInitialized()
        {
            return initialized;
        }

        public bool IsModifiable(String name)
        {
            return factory.IsModifiable(name);
        }

        public IVariableResolver VariableResolver
        {
            get { return variableResolver; }
        }

        public TypeResolver TypeResolver
        {
            get { return typeResolver; }
        }

        public WorkflowDescriptor GetWorkflow(String name)
        {
            WorkflowDescriptor workflow = factory.GetWorkflow(name);

            if (workflow == null)
            {
                throw new FactoryException("Unknown workflow name");
            }

            return workflow;
        }

        public string[] GetWorkflowNames()
        {
            return factory.GetWorkflowNames();
        }

        public IWorkflowStore WorkflowStore
        {
            get
            {
                if (store == null)
                {
                    string clazz = persistenceClass;

                    try
                    {
                        store = (IWorkflowStore)Activator.CreateInstance(Type.GetType(clazz));
                    }
                    catch (Exception ex)
                    {
                        throw new StoreException("Error creating store", ex);
                    }

                    store.Init(persistenceArgs);
                }

                return store;
            }
        }

        public void Load(string filename)
        {
            Stream stream = GetInputStream(filename);

            if (stream == null)
            {
                throw new FactoryException("Cannot find osworkflow.xml configuration file in classpath or in META-INF");
            }

            try
            {
                XmlDocument doc = new XmlDocument();
                doc.Load(stream);

                XmlElement root = (XmlElement)doc.DocumentElement; //.SelectNodes("osworkflow")[0];
                XmlElement p = (XmlElement)root.SelectSingleNode("persistence");
                XmlElement um = (XmlElement)root.SelectSingleNode("usermanager");
                XmlElement resolver = (XmlElement)root.SelectSingleNode("resolver");
                XmlElement factoryElement = (XmlElement)root.SelectSingleNode("factory");

                if (resolver != null)
                {
                    string resolverClass = XMLUtil.GetAttributeValue(resolver, "class");

                    if (resolverClass != null)
                    {
                        variableResolver = (IVariableResolver)Activator.CreateInstance(Type.GetType(resolverClass));
                    }
                }

                persistenceClass = XMLUtil.GetAttributeValue(p, "class");

                XmlNodeList args = p.SelectNodes("property");

                //persistenceArgs = new HashMap();
                for (int i = 0; i < args.Count; i++)
                {
                    XmlElement e = (XmlElement)args[i];
                    persistenceArgs.Add(XMLUtil.GetAttributeValue(e, "key"), XMLUtil.GetAttributeValue(e, "value"));
                }

                if (factoryElement != null)
                {
                    string clazz = null;

                    try
                    {
                        clazz = XMLUtil.GetAttributeValue(factoryElement, "class");

                        if (clazz == null)
                        {
                            throw new FactoryException("factory does not specify a class attribute");
                        }

                        factory = (IWorkflowFactory)Activator.CreateInstance(Type.GetType(clazz));

                        NameValueCollection properties = new NameValueCollection();
                        XmlNodeList props = factoryElement.SelectNodes("property");

                        for (int i = 0; i < props.Count; i++)
                        {
                            XmlElement e = (XmlElement)props[i];
                            properties.Add(XMLUtil.GetAttributeValue(e, "key"), XMLUtil.GetAttributeValue(e, "value"));
                        }

                        factory.Init(properties);
                        factory.InitDone();
                    }
                    catch (FactoryException ex)
                    {
                        throw ex;
                    }
                    catch (Exception ex)
                    {
                        throw new FactoryException("Error creating workflow factory " + clazz, ex);
                    }
                }

                initialized = true;
            }
            catch (FactoryException e)
            {
                throw e;
            }
            catch (Exception e)
            {
                throw new FactoryException("Error in workflow config", e);
            }
            finally
            {
                stream.Close();
            }
        }

        public bool RemoveWorkflow(String workflow)
        {
            return factory.RemoveWorkflow(workflow);
        }

        public bool SaveWorkflow(String name, WorkflowDescriptor descriptor, bool replace)
        {
            return factory.SaveWorkflow(name, descriptor, replace);
        }

        /**
         * Load the default configuration from the current context classloader.
         * The search order is:
         * <li>Specified URL</li>
         * <li>osworkflow.xml</li>
         * <li>/osworkflow.xml</li>
         * <li>META-INF/osworkflow.xml</li>
         * <li>/META-INF/osworkflow.xml</li>
         */
        protected Stream GetInputStream(string filename)
        {
            Stream stream = null;

            if (filename == null)
                filename = "osworkflow.xml";

            try
            {
                stream = new FileInfo(filename).OpenRead();
            }
            catch (Exception ex)
            {
            }

            //ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

            if (stream == null) {
                try {
                    string appPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
                    string fullname = Path.Combine(appPath, filename);
                    stream = new FileInfo(fullname).OpenRead();
                } catch (Exception e) {
                }
            }

            //if (is == null) {
            //    try {
            //        is = classLoader.getResourceAsStream("/osworkflow.xml");
            //    } catch (Exception e) {
            //    }
            //}

            //if (is == null) {
            //    try {
            //        is = classLoader.getResourceAsStream("META-INF/osworkflow.xml");
            //    } catch (Exception e) {
            //    }
            //}

            //if (is == null) {
            //    try {
            //        is = classLoader.getResourceAsStream("/META-INF/osworkflow.xml");
            //    } catch (Exception e) {
            //    }
            //}

            return stream;
        }

        /**
         * Get the workflow factory for this configuration.
         * This method should never ever be called from client code!
         */
        IWorkflowFactory Factory
        {
            get { return factory; }
        }
    }
}
